
package com.tools.cleanmaster.hijack;

import android.content.Context;
import android.os.IBinder;
import android.support.v4.util.SparseArrayCompat;
import android.text.TextUtils;

import com.tools.cleanmaster.utils.Reflections;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Created by apolunor on 15/11/11.
 */
public class FakeAlarmManager implements InvocationHandler {

    // classes we need
    private static final String CLASS_AM_SERVICE_MANAGER = "android.os.ServiceManager";
    private static final String CLASS_AM_I_ALARM_MANAGER_STUB = "android.app.IAlarmManager$Stub";

    private static final String SECURITY_TOO_MANY_ALRAMS = "!@Too many alarms";
    private static final String SECURITY_ACROSS_USERS_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
    private static final String SECURITY_ACROSS_USERS_FULL_PERMISSION = "android.permission.INTERACT_ACROSS_USERS_FULL";
    private static final String SECURITY_DEVICE_POWER_PERMISSION = "android.permission.DEVICE_POWER";
    private static final String SECURITY_INCORRECT_INTERFACE = "Binder invocation to an incorrect interface";

    // methods we need
    private static final String METHOD_AM_GET_SERVICE = "getService";
    private static final String METHOD_AM_TRANSACT = "transact";

    // fields we need
    private static final String FIELD_AM_S_CACHE = "sCache";

    // all versions has
    private static final String TRANSACT_SET = "TRANSACTION_set";
    private static final String TRANSACT_SET_REPEATING = "TRANSACTION_setRepeating";
    private static final String TRANSACT_SET_INEXACT_REPEATING = "TRANSACTION_setInexactRepeating";
    private static final String TRANSACT_REMOVE = "TRANSACTION_remove";

    private static final SparseArrayCompat<String> sTransactionCodes = new SparseArrayCompat<String>();
    private static final Set<String> sTransactCodeNames = new LinkedHashSet<String>();

    static {
        sTransactCodeNames.add(TRANSACT_SET);
        sTransactCodeNames.add(TRANSACT_SET_REPEATING);
        sTransactCodeNames.add(TRANSACT_SET_INEXACT_REPEATING);
        sTransactCodeNames.add(TRANSACT_REMOVE);
    }

    private boolean mIsInitialized = false;
    private static Object sInstance = null;

    private static synchronized Object peekProxyInstance() {
        return sInstance;
    }

    @SuppressWarnings({
            "unchecked", "rawtypes"
    })
    public static synchronized boolean hijackAlarmManager() {
        Object legacyProxy = peekProxyInstance();
        if (legacyProxy != null) {
            return true;
        }

        Class<?> smClass = Reflections.loadClassNoThrow(CLASS_AM_SERVICE_MANAGER);
        Method method = Reflections.findMethodNoThrow(smClass, METHOD_AM_GET_SERVICE, String.class);
        Object target = Reflections.invokeMethodNoThrow(method, null, Context.ALARM_SERVICE);
        if (target == null || !(target instanceof IBinder)) {
            return false;
        }

        Object proxy = createProxyInstance((IBinder) target);
        if (proxy == null) {
            return false;
        }

        Field sCacheField = Reflections.findFieldNoThrow(smClass, FIELD_AM_S_CACHE);
        Object sCache = Reflections.getValueNoThrow(sCacheField, null);
        if (sCache == null || !(sCache instanceof HashMap)) {
            return false;
        }
        ((HashMap) sCache).put(Context.ALARM_SERVICE, proxy);

        return true;
    }

    private static synchronized Object createProxyInstance(IBinder target) {
        ClassLoader targetLoader = target.getClass().getClassLoader();
        Class<?> targetClass = target.getClass();
        FakeAlarmManager amProxy = new FakeAlarmManager(target);
        Class<?>[] interfaces = Reflections.collectInterfaces(targetClass);
        return (sInstance = Proxy.newProxyInstance(targetLoader, interfaces, amProxy));
    }

    private final IBinder mTarget;

    private FakeAlarmManager(IBinder target) {
        mTarget = target;
    }

    // android2.3.2~6.0各版本的下述方法签名一致
    // public IInterface queryLocalInterface(String descriptor);
    // public boolean transact(int code, Parcel data, Parcel reply, int flags) throws
    // RemoteException;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        // 不是transact方法，直接放行
        if (!TextUtils.equals(methodName, METHOD_AM_TRANSACT)) {
            return Reflections.invokeMethod(method, mTarget, args);
        }

        // 判断参数格式是否符合预期，不符合直接放行
        if (args == null || args.length != 4 || !(args[0] instanceof Integer)) {
            return Reflections.invokeMethod(method, mTarget, args);
        }

        ensureInitialized();

        String codeName = sTransactionCodes.get(((Integer) args[0]).intValue());
        if (TextUtils.isEmpty(codeName)) {
            return Reflections.invokeMethod(method, mTarget, args);
        } else if (TextUtils.equals(codeName, TRANSACT_SET)) {
            return handleHijackTransactSet(proxy, method, args);
        } else if (TextUtils.equals(codeName, TRANSACT_SET_REPEATING)) {
            return handleHijackTransactSetRepeating(proxy, method, args);
        } else if (TextUtils.equals(codeName, TRANSACT_SET_INEXACT_REPEATING)) {
            return handleHijackTransactSetInexactRepeating(proxy, method, args);
        } else if (TextUtils.equals(codeName, TRANSACT_REMOVE)) {
            return handleHijackTransactRemove(proxy, method, args);
        } else {
            return Reflections.invokeMethod(method, mTarget, args);
        }
    }

    // android2.3.2
    // public void remove(android.app.PendingIntent operation) throws android.os.RemoteException;
    private Object handleHijackTransactRemove(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            if (TextUtils.indexOf(e.getMessage(), SECURITY_INCORRECT_INTERFACE) >= 0) {
                HijackReporter.reportException(e);
            } else if (TextUtils.indexOf(e.getMessage(), SECURITY_ACROSS_USERS_FULL_PERMISSION) >= 0) {
                HijackReporter.reportException(e);
            } else {
                throw e;
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return result;
    }

    // android2.3.2~4.3.1 4.4以及之后版本没有这个接口
    // public void setInexactRepeating(int type, long triggerAtTime, long interval,
    // android.app.PendingIntent operation) throws android.os.RemoteException;
    private Object handleHijackTransactSetInexactRepeating(Object proxy, Method method,
            Object[] args) throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleSecurityException(e);
        }
        return result;
    }

    // android2.3.2~4.3.1 4.4以及之后版本没有这个接口
    // public void setRepeating(int type, long triggerAtTime, long interval,
    // android.app.PendingIntent operation) throws android.os.RemoteException;
    private Object handleHijackTransactSetRepeating(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleSecurityException(e);
        }
        return result;
    }

    // android5.0.0
    // public void set(int type, long triggerAtTime, long windowLength, long interval,
    // android.app.PendingIntent operation, android.os.WorkSource workSource,
    // android.app.AlarmManager.AlarmClockInfo alarmClock) throws android.os.RemoteException;

    // android4.4
    // public void set(int type, long triggerAtTime, long windowLength, long interval,
    // android.app.PendingIntent operation, android.os.WorkSource workSource) throws
    // android.os.RemoteException;

    // android2.3.2
    // public void set(int type, long triggerAtTime, android.app.PendingIntent operation) throws
    // android.os.RemoteException;
    private Object handleHijackTransactSet(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleSecurityException(e);
        }
        return result;
    }

    private void handleSecurityException(SecurityException e) throws SecurityException {
        if (TextUtils.indexOf(e.getMessage(), SECURITY_TOO_MANY_ALRAMS) >= 0) {
            HijackReporter.reportException(e);
        } else if (TextUtils.indexOf(e.getMessage(), SECURITY_ACROSS_USERS_PERMISSION) >= 0) {
            HijackReporter.reportException(e);
        } else if (TextUtils.indexOf(e.getMessage(), SECURITY_INCORRECT_INTERFACE) >= 0) {
            HijackReporter.reportException(e);
        } else if (TextUtils.indexOf(e.getMessage(), SECURITY_DEVICE_POWER_PERMISSION) >= 0) {
            HijackReporter.reportException(e);
        } else {
            throw e;
        }
    }

    static String buildArgsStringForTest(Object[] args) {
        StringBuilder builder = new StringBuilder();
        if (args == null) {
            builder.append("null");
        } else if (args.length == 0) {
            builder.append("empty");
        } else {
            for (int i = 0; i < args.length; i++) {
                if (!TextUtils.isEmpty(builder)) {
                    builder.append("\n");
                }
                builder.append(i).append(":").append(args[i]);
            }
        }
        return builder.toString();
    }

    // 初始化我们关心的transact的code信息
    private synchronized void ensureInitialized() {
        if (mIsInitialized) {
            return;
        }

        Class<?> amClass = Reflections.loadClassNoThrow(CLASS_AM_I_ALARM_MANAGER_STUB);
        if (amClass == null) {
            mIsInitialized = true;
            return;
        }

        for (String name : sTransactCodeNames) {
            Field field = Reflections.findFieldNoThrow(amClass, name);
            Object code = Reflections.getValueNoThrow(field, null);
            if (!(code instanceof Integer)) {
                continue;
            }
            sTransactionCodes.put(((Integer) code).intValue(), name);
        }

        mIsInitialized = true;
    }
}
